/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include "Node.h"
#include "Creator.h"
#include <Converter.h>
#include <xml/Stream.h>
#include <xml/Saver.h>
#include <logs/Log.h>
#include <Gluck.h>
#include <resource/Manager.h>
#include <lib3d/hard/Hardware.h>
#include <safe_new.h>
#include <textstream/File.h>

#include <boost/scoped_ptr.hpp>

namespace liba {
namespace lib3d {
namespace node {

const Atom Node::node_name( "Node" );
const Atom Node::visibility_name( "Visibility" );
const Atom any_node_name( "#AnyNode#" );

Node::Node( const Atom & name, Node * parent )
:	NodeBase( name ),
	parent( parent ),
	globalMatrixDirty( true ),
#if defined( CACHE_INVERT_GLOBAL_MATRIX )
	invertGlobalMatrixDirty( true ),
#endif
	visibility( true ),
	ignore_children_scene_name(false),
	target(0),
	top_vector(0, 0, 1)
{
	this->RegisterProperty(visibility_name, &this->visibility);
	node_type = node_name;

	if( parent )
		pos_in_parent = parent->children.insert( parent->children.end(), this );
}

Node::~Node()
{
	Clear();
	if( parent )
	{
		parent->children.erase( pos_in_parent );
		parent = 0;
	}
}

Node::Node(const Node & other)
:	NodeBase(other),
	parent(0),
	target(0),
	top_vector(other.top_vector),
	visibility(other.visibility),
	ignore_children_scene_name(false),
	node_type(other.node_type)
{
	this->RegisterProperty(visibility_name, &this->visibility);
	Children::const_iterator chit = other.children.begin();
	for( ; chit != other.children.end(); ++chit)
	{
		Node * child = (*chit)->Clone();
		AddChild(child, true);
	}
}

void Node::CreateProxy(ProxyVisitor * proxyVisitor)
{
	proxyVisitor->CreateNodeProxy(this);
}

Node * Node::Clone()
{
	return new Node(*this);
}

void Node::Clear()
{
	while( !children.empty() )
	{
		Node * node = children.back();
		delete node;
	}
}

void Node::AddChild( Node * node, bool change_world_pos ) 
{
	if(node->GetParent() == this)
		return;

	if( node->GetParent() )
	{	
		node->GetParent()->children.erase( node->pos_in_parent );
		node->parent = 0;
	}
	
	Children::iterator it = children.insert( children.end(), node );
	node->pos_in_parent = it;
	node->parent = this;

	if ( change_world_pos )
		node->InvalidateGlobalMatrix();
}

void Node::RemoveChild( Node * node, bool change_world_pos )
{
	if(node->GetParent() == NULL)
		return;
	
	if ( change_world_pos )
		node->InvalidateGlobalMatrix();
	
	node->GetParent()->children.erase( node->pos_in_parent );
	node->parent = NULL;
}
	
void Node::RemoveFromParent( bool change_world_pos )
{
	if(GetParent() != NULL)
		GetParent()->RemoveChild(this);
}
	
Node * Node::FindNode(const Atom & name, bool recursion )
{
	if( name == this->GetName())
	{
		return this;
	}
	Children::const_iterator it = children.begin();
	for( ; it != children.end(); ++it )
	{
		if( (*it)->GetName() == name )
			return *it;
		if ( recursion )
		{
			Node * node = (*it)->FindNode( name, recursion );
			if ( node )
				return node;
		}
	}
	return 0;
}

Vector Node::GetGlobalPosition()const
{
	if(GetParent() == NULL)
		return GetPosition();
	
	return GetParent()->GetGlobalMatrix() * GetPosition();
}

Matrix Node::GetRotationMatrix() const
{
	Matrix rotationMatrix = Matrix::rotate(rotation);

	for(Node * current = parent; current != NULL; current = current->GetParent())
		rotationMatrix = Matrix::rotate(current->rotation) * rotationMatrix;

	return rotationMatrix;
}

Vector Node::GetGlobalRotation()const
{
	if(GetParent() == NULL)
		return GetRotation();

	return Matrix::factorization(GetRotationMatrix());
}

Vector Node::GetGlobalScale()const
{
	if(GetParent() == NULL)
		return GetScale();

	Vector parentScale = GetParent()->GetGlobalScale();

	return Vector(scale.Get().x * parentScale.x, scale.Get().y * parentScale.y, scale.Get().z * parentScale.z);
}

void Node::ChangeGlobalPosition(const Vector & position)
{
	if(GetParent() == NULL)
		return ChangePosition(position);

	Matrix parentMatrix = Matrix::invert(GetParent()->GetGlobalMatrix());
	ChangePosition(parentMatrix * position);
}

void Node::ChangeGlobalRotation(const Vector & rotation)
{
	if(GetParent() == NULL)
		return ChangeRotation(rotation);

	ChangeRotation(Matrix::factorization(Matrix::invert(GetParent()->GetRotationMatrix()) * Matrix::rotate(rotation)));
}

void Node::ChangeGlobalScale(const Vector & scale)
{
	if(GetParent() == NULL)
		return ChangeScale(scale);

	Vector parentScale = GetParent()->GetGlobalScale();
	ChangeScale(Vector(scale.x / parentScale.x, scale.y / parentScale.y, scale.z / parentScale.z));
}

void Node::RenderToList( hard::Hardware * hardware, hard::RenderList * render_list )
{
	Children::iterator it = children.begin();
	for ( ; it != children.end(); ++it )
	{
		Node * node = *it;

		if(node->visibility)
			node->RenderToList( hardware, render_list );
	}
}

animate::Node * Node::FindAnimateNode( const Atom & name, bool recursion )
{
	if( name == any_node_name )
		return this;

	Children::iterator it = children.begin();
	for( ; it != children.end(); ++it )
	{
		if( (*it)->GetName() == name )
			return *it;
		if ( recursion )
		{
			animate::Node * node = (*it)->FindAnimateNode( name, recursion );
			if ( node )
				return node;
		}
	}
	return 0;
}

const Matrix& Node::GetGlobalMatrix() const
{
	if (!globalMatrixDirty)
		return globalMatrix;
	
	Matrix mat = GetLocalMatrix();
	Node * par = parent;

	for( ; par ; par = par->GetParent() )
		mat = par->GetLocalMatrix() * mat;

	this->globalMatrix = mat;
	this->globalMatrixDirty = false;
	return this->globalMatrix;
}

void Node::SetGlobalMatrix( const Matrix & mat )
{
	if (this->parent)
		this->set_matrix( Matrix::invert( parent->GetGlobalMatrix() ) * mat );
	else
		this->set_matrix(mat);
}

#if defined( CACHE_INVERT_GLOBAL_MATRIX )
const Matrix& Node::GetInvertGlobalMatrix() const
{
	if (!invertGlobalMatrixDirty)
		return invertGlobalMatrix;

	this->invertGlobalMatrix = Matrix::invert( GetGlobalMatrix() );
	this->invertGlobalMatrixDirty = false;

	return this->invertGlobalMatrix;
}
#else
Matrix Node::GetInvertGlobalMatrix() const
{
	return Matrix::invert( GetGlobalMatrix() );
}
#endif

void Node::OnLocalMatrixInvalidated()
{
	this->InvalidateGlobalMatrix();
}

void Node::InvalidateGlobalMatrix()
{
	this->globalMatrixDirty = true;
#if defined( CACHE_INVERT_GLOBAL_MATRIX )
	this->invertGlobalMatrixDirty = true;
#endif
	std::for_each(children.begin(), children.end(), std::mem_fun(&Node::InvalidateGlobalMatrix));
	this->OnGlobalMatrixInvalidated();
}

bool Node::on_attribute( xml::Provider * prov, const std::string & name, const std::string & value )
{
	if ( name == "Visibility" )
	{
		bool visibility;
		bool res = converter::convert( value, &visibility );
		if (res)
			this->visibility = visibility;
		return res;
	}
	if ( name == "Children" )
	{
		children_scene_name = value;
		boost::scoped_ptr<resource::Resource> res(resource::Manager::create_resource( value ));
		return xml::StreamResource( res.get() ).load( &xml::Root(this, "Scene") );
	}
	if ( name == "Target" )
	{
		target_name = value;
		return true;
	}
	if( name == "TopVector" )
	{
		return converter::convert(value, &top_vector);
	}
	return NodeBase::on_attribute( prov, name, value );
}

bool Node::on_node( xml::Provider * prov, const std::string & name )
{
	if(name == "Scene")
		return prov->parse_object(this);

	Node * child = Factory::create( this, Atom( name ) );
	if( child )
		return prov->parse_object( children.back() );
	return false;
}

void Node::on_save_attributes( xml::SaverBase * saver )const
{
	if(visibility != true)
		saver->save_attribute("Visibility", visibility.Get());

	if(!target_name.empty())
		saver->save_attribute("Target", target_name);

	if(top_vector != Vector(0, 0, 1))
		saver->save_attribute("TopVector", top_vector);

	if(!children_scene_name.empty() && !ignore_children_scene_name)
		saver->save_attribute("Children", children_scene_name);

	NodeBase::on_save_attributes(saver);
}

void Node::on_save_nodes( xml::SaverBase * saver )const
{
	if(!children_scene_name.empty() && !ignore_children_scene_name)
	{
		ignore_children_scene_name = true;

		textstream::File<char> sceneStream(children_scene_name);
		xml::SaverText<char>(&sceneStream).save_node("Scene", this); 

		ignore_children_scene_name = false;

		return;
	}

	Children::const_iterator it = ChildrenBegin();
	for(; it != ChildrenEnd(); ++it)
	{
		saver->save_node((*it)->GetType().get_str(), *it);
	}

	NodeBase::on_save_nodes(saver);
}

void Node::ChangeRotation( const Vector & target_pos, const Vector & top )
{
	rotation = Vector::zero();
	Matrix parent_mat = GetParent()->GetGlobalMatrix();
	Matrix inv_parent_mat = Matrix::invert(parent_mat);

	Vector dir = inv_parent_mat * target_pos - position.Get();
	dir.normalize();
	Vector top_dir = top * inv_parent_mat;
	top_dir.normalize();
	Vector right = Vector::cross( dir, top_dir );
	right.normalize();
	top_dir = Vector::cross( right, dir );

	Matrix mat = Matrix::identity();
	mat.rc(0,0) = right.x;
	mat.rc(1,0) = right.y;
	mat.rc(2,0) = right.z;
	mat.rc(0,1) = top_dir.x;
	mat.rc(1,1) = top_dir.y;
	mat.rc(2,1) = top_dir.z;
	mat.rc(0,2) = dir.x;
	mat.rc(1,2) = dir.y;
	mat.rc(2,2) = dir.z;

	math::Vector3<float> fact = Matrix::factorization(mat);
	fact.x = 3.1415926f - fact.x;
	this->rotation = fact;
	this->InvalidateLocalMatrix();
}

Node * CreateFromResource( const Atom & name, Node * parent, const filesystem::String & resource_name )
{
	Node * nod = new Node( name, parent );
	boost::scoped_ptr<resource::Resource> res(resource::Manager::create_resource( resource_name ));
	if( !xml::StreamResource( res.get() ).load( nod ) )
	{
		delete nod; nod = 0;
		THROW(GluckBadResource(__FUNCTION__, "error parsing resource", resource_name ));
	}
	return nod;
}

} // namespace node
} // namespace lib3d
} // namespace liba

